﻿using System;
using System.Collections.Generic;
using System.IO;

using HappyTemplate.BuiltInFunctions;

namespace HappyTemplate
{
	public class TemplateParser
	{
		readonly TemplateLexer _lexer;
		readonly TemplateErrorCollector _errorCollector;
		readonly Dictionary<string, BuiltInFunctionBase> _builtInFunctions = 
			new Dictionary<string, BuiltInFunctionBase>();

		internal TemplateParser(TemplateLexer lexer, SourceErrorCollection errors)
		{
			_lexer = lexer;
			_errorCollector = new TemplateErrorCollector(errors);

			_builtInFunctions = BuiltInFunctionBase.DiscoverBuiltinFunctions(
				System.Reflection.Assembly.GetExecutingAssembly());
		}

		public void AddBuiltInFunction(BuiltInFunctionBase bif)
		{
			if(_builtInFunctions.ContainsKey(bif.Name))
				throw new BuiltInFunctionException("Built-in function \"{0}\" already exists", bif.Name);

			_builtInFunctions.Add(bif.Name, bif);
		}

		public static TemplateSet ParseTemplateSet(string templateFile, out SourceErrorCollection errors)
		{
			if(templateFile == null)
				throw new ArgumentNullException("templateFile");
			
			StreamReader templateStreamReader = new StreamReader(templateFile);

			return ParseTemplateSet(templateStreamReader, templateFile, out errors);
		}

		public static TemplateSet ParseTemplateSet
		(
			TextReader textReader, 
			string sourceName,
			out SourceErrorCollection errors
		)
		{
			if(textReader == null)
				throw new ArgumentNullException("textReader");
			if(sourceName == null)
				throw new ArgumentNullException("sourceName");

			errors = new SourceErrorCollection();
			InputReader reader = new InputReader(textReader, sourceName);
			TemplateLexer templateLexer = new TemplateLexer(reader, errors);
			TemplateParser templateParser = new TemplateParser(templateLexer, errors);
			TemplateSet retval = null;
			try
			{
				retval = templateParser.Parse();
			}
			catch(AbortParseException )
			{
				//Left blank intentionally because AbortParseExceptions should never
				//be allowed to bubble up to the caller here.
			}
			return retval;
		}

		internal TemplateSet Parse()
		{
			List<Template> templates = new List<Template>();
			List<Lookup> lookups = new List<Lookup>();

			while(!_lexer.Eof)
			{
				if(EatNextTokenIf(TokenType.KeywordLookup))
					lookups.Add(ParseLookup());
				else
					templates.Add(ParseTemplate());				
			}

			return new TemplateSet(lookups.ToArray(), templates.ToArray(), _builtInFunctions);
		}

		internal Token ErrorIfMultipart(Token t)
		{
			if(t.TokenType == TokenType.Identifier && t.Parts.Length > 1)
				_errorCollector.AddMultipartIdentifierNotAllowedInThisContext(t.Location);

			return t;
		}

		internal Template ParseTemplate()
		{
			List<ExecutableNonTerminalBase> sections = new List<ExecutableNonTerminalBase>();
			Token identifier = ErrorIfMultipart(Expect(TokenType.Identifier, Resources.MiscMessages.TemplateIdentifier));
			List<TemplateParameter> parameters = new List<TemplateParameter>();

			SourceLocation sectionStartsAt = Expect(TokenType.OpenParen, "(").Location;

			//Parse parameters
			bool parseMore = !EatNextTokenIf(TokenType.CloseParen);
			while (parseMore)
			{
				_lexer.FailIfEof(sectionStartsAt);
				Token tmp = ErrorIfMultipart(Expect(TokenType.Identifier, Resources.MiscMessages.ParameterIdentifier));
				parameters.Add(new TemplateParameter(tmp.Location, tmp.Text));

				if (Expect(TokenType.CloseParen, TokenType.Comma, Resources.MiscMessages.CommaOrCloseParen).TokenType == TokenType.CloseParen)
					parseMore = false;
			}

			//Parse body
			sectionStartsAt = Expect(TokenType.BeginTemplate, "<{").Location;
			bool keepParsing = true;
			while(keepParsing)
			{
                _lexer.FailIfEof(sectionStartsAt);
				sections.Add(ParseSection());

				if (EatNextTokenIf(TokenType.EndTemplate))
					keepParsing = false;
				
			}

			return new Template(identifier.Location, identifier.Text, parameters.ToArray(), sections.ToArray());
		}


		private ExecutableNonTerminalBase ParseSection()
		{
			Token temp;
			_lexer.FailIfEof();

			ExecutableNonTerminalBase retval;

			switch (_lexer.PeekToken(0).TokenType)
			{
			case TokenType.Verbatim:
				temp = _lexer.NextToken();
				retval = new VerbatimSection(temp.Location, temp.Text);
				break;
			case TokenType.Identifier:
			case TokenType.LiteralString:
			case TokenType.LiteralInt32:
			case TokenType.LiteralBool:
			case TokenType.LiteralDouble:
				retval = ParseExpansion();
				break;
			case TokenType.KeywordIf:
				retval = ParseIf();
				break;
			case TokenType.KeywordForEach:
				retval = ParseForEach();
				break;
			case TokenType.KeywordSet:
			case TokenType.KeywordGlobal:
				retval = ParseSet();
				break;
			default:
				temp = _lexer.NextToken();
				retval = new UnexpectedToken(temp.Location);
				_errorCollector.AddUnexpectedToken(temp);
				break;
				//throw new UnhandledCaseSourceException(t.Location, String.Format("Unkwown TokenType:  {0}, {1}", t.TokenType, t.Text));
			}	
			DebugAssert.IsNotNull(retval, "return value is null");
			return retval;
		}


		private Token Expect(TokenType tokenType1, TokenType tokenType2, string whatWasExpected)
		{
			Token next = _lexer.NextToken();
			if (next.TokenType == tokenType1 || next.TokenType == tokenType2)
				return next;

			_errorCollector.AddExpected(whatWasExpected, next);
			throw new AbortParseException(next.Location);
		}

		private Token Expect(TokenType tokenType, string whatWasExpected)
		{
			Token next = _lexer.NextToken();
			if(next.TokenType == tokenType) 
				return next;

			_errorCollector.AddExpected(whatWasExpected, next);
			throw new AbortParseException(next.Location);
		}

		private bool EatNextTokenIf(TokenType tokenType)
		{
			if (_lexer.PeekToken().TokenType == tokenType)
			{
				_lexer.NextToken();
				return true;
			}

			return false;
		}

		SetStatement ParseSet()
		{
			Token setType =
				Expect(TokenType.KeywordSet, TokenType.KeywordGlobal, Resources.MiscMessages.SetOrGlobal);
			Token identifier =
				ErrorIfMultipart(Expect(TokenType.Identifier, Resources.MiscMessages.AnIdentifier));
			Expect(TokenType.OperatorAssign, "=");
			ExpansionBase value = ParseExpansion();

			return new SetStatement(setType.Location,
			                        setType.TokenType == TokenType.KeywordSet
			                        	? SetStatement.SetType.Local
			                        	: SetStatement.SetType.Global,
			                        identifier.ToIdentifier(),
			                        value);
		}

		private IfStatement ParseIf()
		{
			Expect(TokenType.KeywordIf, "if");
			Expect(TokenType.OpenParen, "(");
			bool inverted = EatNextTokenIf(TokenType.Bang);
			ExpansionBase value = ParseExpansion();
			Expect(TokenType.CloseParen, ")");

			List<ExecutableNonTerminalBase> trueSections = new List<ExecutableNonTerminalBase>();
			List<ExecutableNonTerminalBase> falseSections = new List<ExecutableNonTerminalBase>();

			while (_lexer.PeekToken().TokenType != TokenType.KeywordEndIf && _lexer.PeekToken().TokenType != TokenType.KeywordElse)
			{
				_lexer.FailIfEof();
				trueSections.Add(ParseSection());
			}
			if (_lexer.PeekToken().TokenType == TokenType.KeywordElse)
			{
				_lexer.NextToken();
				while(_lexer.PeekToken().TokenType != TokenType.KeywordEndIf)
				{
					_lexer.FailIfEof();
					falseSections.Add(ParseSection());
				}
			}

			_lexer.NextToken();


			return new IfStatement(inverted, value, trueSections.ToArray(), falseSections.ToArray());
		}

		private ForEachStatement ParseForEach()
		{
			Expect(TokenType.KeywordForEach, "foreach");
			SourceLocation startsAt = Expect(TokenType.OpenParen, "(").Location;
			Token loopIdentifier = ErrorIfMultipart(Expect(TokenType.Identifier, Resources.MiscMessages.AnIdentifier));
			Expect(TokenType.KeywordIn, "in");
			ExpansionBase enumerable = ParseExpansion();
			ExpansionBase between = null;

			if(EatNextTokenIf(TokenType.KeywordBetween))
				between = ParseExpansion();
			
			Expect(TokenType.CloseParen, ")");

			List<ExecutableNonTerminalBase> sections = new List<ExecutableNonTerminalBase>();

			while (_lexer.PeekToken().TokenType != TokenType.KeywordNext)
			{
				_lexer.FailIfEof(startsAt);
				sections.Add(ParseSection());
			}
			_lexer.NextToken();

			return new ForEachStatement(loopIdentifier.Location, loopIdentifier.ToIdentifier(),
				enumerable, between, sections.ToArray());
		}


		private ExpansionBase ParseExpansion()
		{
			Token firstToken = _lexer.NextToken();
			List<ExpansionBase> arguments = new List<ExpansionBase>();
			ExpansionBase retval;
			switch(firstToken.TokenType)
			{
				case TokenType.Identifier:
					if (EatNextTokenIf(TokenType.OpenParen))
					{
						bool parseMore = !EatNextTokenIf(TokenType.CloseParen);
						while (parseMore)
						{
							arguments.Add(ParseExpansion());

							parseMore = Expect(TokenType.Comma, TokenType.CloseParen, 
								Resources.MiscMessages.CommaOrCloseParen).TokenType != TokenType.CloseParen;
						}

						Identifier identifier = firstToken.ToIdentifier();
						if (_builtInFunctions.ContainsKey(identifier.Text))
							retval = new BuiltInFunctionExpansion(identifier, arguments.ToArray());
						else
							retval = new TemplateExpansion(identifier, arguments.ToArray());
					} else if(EatNextTokenIf(TokenType.OpenBracket))
					{
						ExpansionBase inputExpansion = ParseExpansion();
						Expect(TokenType.CloseBracket, "]");
						retval = new LookupExpansion(firstToken.ToIdentifier(), inputExpansion);
					}
					else
						retval = new VariableExpansion(firstToken.ToIdentifier());
					break;
				case TokenType.LiteralBool:
					switch(firstToken.Text)
					{
						case "true":
							retval = new LiteralExpansion<bool>(firstToken.Location, true);
							break;
						case "false":
							retval = new LiteralExpansion<bool>(firstToken.Location, false);
							break;
						default:
							throw new UnhandledCaseSourceException(firstToken.Location);
					}
					break;
				case TokenType.LiteralInt32:
					int intValue;
					try
					{
						intValue = Int32.Parse(firstToken.Text);	
					} catch(Exception e)
					{
						throw new InternalSourceException(firstToken.Location, e,
							"Failed to parse an Int32 from \"{0}\" TokenType is LiteralInt32?!?!", firstToken.Text);
					}
					retval = new LiteralExpansion<int>(firstToken.Location, intValue);
					break;
				case TokenType.LiteralDouble:
					double doubleValue;
					try
					{
						doubleValue = Double.Parse(firstToken.Text);	
					} catch(Exception e)
					{
						throw new InternalSourceException(firstToken.Location, e,
							"Failed to parse a Double from \"{0}\" but TokenType is LiteralDouble?!?!", firstToken.Text);
					}
					retval = new LiteralExpansion<double>(firstToken.Location, doubleValue);
					break;
				case TokenType.LiteralString:
					retval = new LiteralExpansion<string>(firstToken.Location, firstToken.Text);
					break;
				case TokenType.KeywordNull:
					retval = new NullExpansion(firstToken.Location);
					break;
				case TokenType.BeginTemplate:
					retval = ParseAnonymousTemplateExpansion(firstToken.Location);
					break;
				default:
					_errorCollector.AddUnexpectedToken(firstToken);
					throw new AbortParseException(firstToken.Location);
			}
			return retval;
		}

		private AnonymousTemplateExpansion ParseAnonymousTemplateExpansion(SourceLocation startsAt)
		{
			List<ExecutableNonTerminalBase> sections = new List<ExecutableNonTerminalBase>();

			//the <{ already parsed during call to ParseExpansion()
			//SourceLocation sectionStartsAt = Expect(TokenType.BeginTemplate, "<{").Location;
			bool keepParsing = true;
			while(keepParsing)
			{
				_lexer.FailIfEof(startsAt);
				sections.Add(ParseSection());

				if (EatNextTokenIf(TokenType.EndTemplate))
					keepParsing = false;
			}
			return new AnonymousTemplateExpansion(startsAt, sections.ToArray());
		}

		private Lookup ParseLookup()
		{
			Token ident = ErrorIfMultipart(Expect(TokenType.Identifier, Resources.MiscMessages.AnIdentifier));
			Expect(TokenType.OpenBrace, "{");
			string defaultValue = null;
			Dictionary<string, string> values = new Dictionary<string, string>();

			bool parseMore = true;
			do
			{
				Token inputValue = Expect(TokenType.LiteralString, TokenType.KeywordDefault, Resources.MiscMessages.LiteralString);
				Expect(TokenType.OperatorAssign, "=");
				Token outputValue = Expect(TokenType.LiteralString, Resources.MiscMessages.LiteralString);

				if(inputValue.TokenType == TokenType.LiteralString)
				{
					if(values.ContainsKey(inputValue.Text))
						_errorCollector.AddDuplicateInputValue(inputValue);
					else
						values.Add(inputValue.Text, outputValue.Text);
				}
				else if(inputValue.TokenType == TokenType.KeywordDefault)
				{
					if(defaultValue != null)
						_errorCollector.AddDefaultValueMayOnlyBeSpecifiedOnce(inputValue.Location);
					else
						defaultValue = outputValue.Text;
				}
				else
					throw new UnhandledCaseSourceException(inputValue.Location);

				if (Expect(TokenType.Comma, TokenType.CloseBrace, Resources.MiscMessages.CommaOrCloseBrace).TokenType == TokenType.CloseBrace)
					parseMore = false;
					
			} while (parseMore);

			return new Lookup(ident.ToIdentifier(), values, defaultValue);
		}
	}
}
